package model;

import java.util.Collections;
import java.util.List;

import math.RMSE;
import data.DataBig;

public class MFBig {

	int nuser;
	int maxuid;
	int nitem;
	
	double[] ubias;
	double[] ibias;
	
	double[] ufactor;
	double[] ifactor;	
	
	double avgTrain;
	
	int k;

	public MFBig(List<DataBig> train, int maxuid2, int maxiid, byte k, double gamma, double lambda) {

		nuser = maxuid2;
		maxuid = maxuid2;
		nitem = maxiid;
		
		ubias = new double[maxuid];
		ibias = new double[nitem];
		
		this.k = k;
		
		double sumTrain = 0.0;
		for(DataBig data : train){
			double rat = data.rat;
			sumTrain += rat;
		}			
		
		avgTrain = sumTrain/train.size();
		
		ufactor = new double[maxuid * k];
		ifactor = new double[nitem * k];	

		for(int i=0; i<maxuid; i++) {
			ubias[i] = 0.0;
		}

		for(int i=0; i<nitem; i++) {
			ibias[i] = 0.0;
		}
		
		for(int i=0; i<maxuid*k; i++) {
			ufactor[i] = (Math.random()-0.5)/k;
		}
		
		for(int i=0; i<nitem*k; i++) {
			ifactor[i] = (Math.random()-0.5)/k;
		}
		
		//initial error
		System.out.println("Initial training RMSE: " + RMSE.RMSE(train, avgTrain, ubias, ibias, ufactor, ifactor, k));
		double formerrmse=0;
		double latterrmse=0;

		for (int it=1;it<=100;it++){
			Collections.shuffle(train);
			formerrmse = latterrmse;
//			formerrmse = RMSE.RMSE(train, avgTrain, ubias, ibias, ufactor, ifactor, k);
			double SE = 0;
			for(int i=0; i<train.size(); i++) {
				int uid = train.get(i).uid;
				int iid = train.get(i).iid;
				double rat = train.get(i).rat;

				double pred = 0.0;
				pred += avgTrain;
				pred += ubias[uid-1];
				pred += ibias[iid-1];
				for(int j=0; j<k; j++) {
					pred += ufactor[(uid-1)*k+j]*ifactor[(iid-1)*k+j];
				}
				double err = rat - pred;
				SE = err * err;
				
				ubias[uid-1] =  ubias[uid-1] + gamma*(err-lambda*ubias[uid-1]);
				ibias[iid-1] =  ibias[iid-1] + gamma*(err-lambda*ibias[iid-1]);
				
				for(int j=0; j<k; j++) {
					double newufactor = ufactor[(uid-1)*k+j] + gamma*(err*ifactor[(iid-1)*k+j] - lambda*ufactor[(uid-1)*k+j]);
					double newifactor = ifactor[(iid-1)*k+j] + gamma*(err*ufactor[(uid-1)*k+j] - lambda*ifactor[(iid-1)*k+j]);
					ufactor[(uid-1)*k+j] = newufactor;
					ifactor[(iid-1)*k+j] = newifactor;
				}
			}
			latterrmse = RMSE.RMSE(train, avgTrain, ubias, ibias, ufactor, ifactor, k);
//			latterrmse += SE;
			if ((it > 1) && (formerrmse - latterrmse < 0.0001)){
				System.out.println("Converged at step " + it);
				break;
			}
		}
		
		//error after learning
		System.out.println("Training RMSE after learning: " + RMSE.RMSE(train, avgTrain, ubias, ibias, ufactor, ifactor, k));
	}

	public int getNuser() {
		return nuser;
	}

	public int getMaxuid() {
		return maxuid;
	}

	public int getNitem() {
		return nitem;
	}

	public double[] getUbias() {
		return ubias;
	}

	public double[] getIbias() {
		return ibias;
	}

	public double[] getUfactor() {
		return ufactor;
	}

	public double[] getIfactor() {
		return ifactor;
	}

	public double getAvgTrain() {
		return avgTrain;
	}

	public int getK() {
		return k;
	}

}
